<?php

namespace liketp;

use Closure;
use liketp\exception\Exception;
use liketp\route\Domain;
use liketp\route\Rule;
use liketp\route\RuleGroup;

class Route
{
    protected $app = null;
    protected $domains = [];
    protected $group = null;
    protected $request = null;

    protected $host = '';

    protected $config = [
        'lazy' => false,
        'route_complete_match' => false,
        'url_route_must' => true
    ];

    public function __construct(App $app)
    {
        $this->app = $app;
        $this->setDefaultGroup();
    }


    //------------------------------------------------ external methods
    public function rule($rule, $route, $method)
    {
        return $this->group->addRule($rule, $route, $method);
    }

    public function get($rule, $route)
    {
        return $this->rule($rule, $route, 'GET');
    }

    public function post($rule, $route)
    {
        return $this->rule($rule, $route, 'POST');
    }

    public function head() {}
    public function options() {}
    public function put() {}
    public function delete() {}

    public function group($name, $handler = null)
    {
        if ($name instanceof Closure) {
            $handler = $name;
            $name = '';
        }
        if ($handler == null) return;
        $group = new RuleGroup($this, $this->group, $name, $handler);
        $group->lazy($this->config('lazy'));
        return $group;
    }

    public function view(string $rule, string $tpl = '', array $vars = [])
    {
        return $this->rule($rule, function () use ($tpl, $vars) {
            return Response::create(null, 'view')->tpl($tpl)->data($vars);
        }, 'GET');
    }

    public function redirect(string $rule, string $url, int $status = 302)
    {
        return $this->rule($rule, function () use ($url, $status) {
            return Response::create($url, 'redirect', $status);
        }, '*');
    }

    public function miss($route, $method = '*')
    {
        return $this->group->miss($route, $method);
    }


    //------------------------------------------------ internal methods
    public function app()
    {
        return $this->app;
    }

    public function setGroup(RuleGroup $group)
    {
        $this->group = $group;
    }

    public function getGroup()
    {
        return $this->group;
    }

    public function dispatch(Request $request, $withRoute = null)
    {
        $this->request = $request;
        $this->host = $this->request->host(true);
        $this->init();

        if ($withRoute) {
            if ($withRoute instanceof Closure) {
                $withRoute();
            }
            $dispatch = $this->check();
        } else {
            $dispatch = $this->url($this->path());
        }

        $dispatch->init($this->app);

        return $this->app->middleware->pipeline('route')
            ->send($request)
            ->then(function () use ($dispatch) {
                return $dispatch->run();
            });
    }

    public function config(string $key = '', $default = '')
    {
        if (empty($key)) return $this->config;
        return $this->config[$key] ?? $default;
    }

    //------------------------------------------------ protected
    protected function init() {}

    protected function setDefaultGroup()
    {
        $domain = '-';
        if (isset($this->domains[$domain]) == false) {
            $this->domains[$domain] = new Domain($this, $domain);
        }
        return $this->setGroup($this->domains[$domain]);
    }

    protected function url($url)
    {
        $controller = $this->request->controller();
        $action = $this->request->action();
        return $this->app->invokeControllerAction($controller, $action);
    }

    protected function path()
    {
        return $this->request->pathinfo();
    }

    protected function check()
    {
        $url = $this->path();
        $completeMatch = $this->config('route_complete_match', false);
        $result = $this->checkDomain()->check($this->request, $url, $completeMatch);

        if ($result !== false) {
            return $result;
        } elseif ($this->config('url_route_must', false)) {
            throw new Exception('Route Not Found');
        }
        return $this->url($url);
    }

    protected function checkDomain()
    {
        $domain = $this->host;
        $targetDomain = $this->domains[$domain] ?? $this->domains['-'];
        return $targetDomain;
    }
}
